using System;
using System.Threading;
using System.Net.Sockets;
using System.Diagnostics;
using BassMati.Utilities;

namespace BassMati.Server
{
	/// <summary>
	/// Represents a client connecton, maintained by the server.
	/// </summary>
	public class ClientConnection
	{
		public ClientConnection ( Server server, int id, TcpClient clientConnection )
		{
			Trace.WriteLineIf ( traceLevel.TraceInfo, "ClientConnection.ctor() enter", "Info (Server.exe)" );
			Trace.Indent ( );
			this.server = server;
			this.id = id;
			this.connection = clientConnection;
			this.thread = new Thread( new ThreadStart( this.Work ));
			this.thread.Name = string.Format( "Client {0} Thread", this.id );
			Trace.WriteLineIf( traceLevel.TraceInfo, string.Format( "Thread created ({0}).", this.thread.Name ), "Thread Info" );
			this.connection.ReceiveTimeout = 0; // 10000;
			Trace.Unindent ( );
			Trace.WriteLineIf ( traceLevel.TraceInfo, "ClientConnection.ctor() exit", "Info (Server.exe)" );
		}

		protected System.Diagnostics.TraceSwitch traceLevel = new TraceSwitch( "ClientConnectionTraceLevel", "Controls trace information output level." );
		protected Server server;
		protected int id;
		protected TcpClient connection;
		protected byte [ ] buffer = new byte [ 1024 ];
		protected Thread thread;

		public int ID { get { return this.id; } }

		
		#region - Events -

		/// <summary>
		/// Defines the event argument of a Disconnecting event handler.
		/// </summary>
		public struct DisconnectingEventArgs
		{
			public int ClientID;
			public string Reason;
			public DisconnectingEventArgs ( int clientID, string reason ) { this.ClientID = clientID; this.Reason = reason; }
		}

		/// <summary>
		/// Defines the signature of a Disconnecting event handler.
		/// </summary>
		public delegate void DisconnectingEventHandler ( object sender, DisconnectingEventArgs e );

		/// <summary>
		/// Fires when a client disconnects from the server.
		/// </summary>
		public event DisconnectingEventHandler Disconnecting;
			
		/// <summary>
		/// Fires the ClientDisconnecting event.
		/// </summary>
		/// <param name="client"></param>
		protected void OnDisconnecting( string reason )
		{
			Event.Fire ( this.Disconnecting, new object [ ] { this, new DisconnectingEventArgs( this.ID, reason ) } );
		}

		/// <summary>
		/// Defines the event argument of a Disconnected event handler.
		/// </summary>
		public struct DisconnectedEventArgs
		{
			public int ClientID;
			public DisconnectedEventArgs ( int clientID ) { this.ClientID = clientID; }
		}

		/// <summary>
		/// Defines the signature of a Disconnected event handler.
		/// </summary>
		public delegate void DisconnectedEventHandler ( object sender, DisconnectedEventArgs e );

		/// <summary>
		/// Fires when a client has disconnected from the server.
		/// </summary>
		public event DisconnectedEventHandler Disconnected;
			
		/// <summary>
		/// Fires the ClientDisconnected event.
		/// </summary>
		/// <param name="client"></param>
		protected void OnDisconnected ( )
		{
			Event.Fire( this.Disconnected, new object [ ] { this, new DisconnectedEventArgs( this.ID ) } );
		}

		#endregion - Events -

		public void Start ( )
		{
			Trace.WriteLineIf ( traceLevel.TraceInfo, "ClientConnection.Start() enter", "Info (Server.exe)" );
			Trace.Indent ( );
			try
			{
				this.thread.Start( );
			}
			catch ( System.Exception ex )
			{
				Trace.WriteLine ( TraceFormatter.FormatException( ex), "Error (Server.exe)" );
				throw ( ex );
			}
			finally
			{
				Trace.Unindent ( );
				Trace.WriteLineIf ( traceLevel.TraceInfo, "ClientConnection.Start() exit", "Info (Server.exe)" );
			}
		}

		public void Stop ( )
		{
			Trace.WriteLineIf ( traceLevel.TraceInfo, "ClientConnection.Stop() enter", "Info (Server.exe)" );
			Trace.Indent ( );
			try
			{
				Trace.WriteLineIf ( traceLevel.TraceInfo, string.Format( "Stopping client {0}.", this.id ), "Info (Server.exe)" );
				this.connection.GetStream().Close();
				// This will raise an exception in Work().
				this.connection.Close( );
				bool success = this.thread.Join( 10000 );
				if ( ! success )
				{
					Trace.WriteLineIf ( traceLevel.TraceInfo, "Aborting the client.", "Info (Server.exe)" );
					this.thread.Abort( );
					success = this.thread.Join( 10000 );
				}
				Trace.WriteLineIf ( traceLevel.TraceInfo, string.Format( "ClientConnection {0} shutdown {1}.", this.id, success ? "succeeded" : "failed" ), "Info (Server.exe)" );
			}
			catch ( System.Exception ex )
			{
				Trace.WriteLine ( TraceFormatter.FormatException( ex), "Error (Server.exe)" );
				throw ( ex );
			}
			finally
			{
				Trace.Unindent ( );
				Trace.WriteLineIf ( traceLevel.TraceInfo, "ClientConnection.Stop() exit", "Info (Server.exe)" );
			}
		}

		
		protected void Work( )
		{		
			Trace.WriteLineIf ( traceLevel.TraceInfo, string.Format( "ClientConnection.Work() enter\r\n  Running on thread {0} - {1}", AppDomain.GetCurrentThreadId( ), Thread.CurrentThread.Name ), TraceFormatter.FormatCategory( "Info" ));
			try
			{
				Trace.WriteLineIf ( traceLevel.TraceInfo, string.Format ( "ClientConnection {0} service running.", this.id ), "Info (Server.exe)" );
				NetworkStream stream = this.connection.GetStream ( );
				// Loop to receive all the data sent by the client.
				int bytesReceived;
				while (( bytesReceived = stream.Read ( buffer, 0, buffer.Length )) != 0 ) 
				{  
					Trace.WriteLineIf ( traceLevel.TraceInfo, string.Format ( "ClientConnection {0} sent data.", this.id ), "Info (Server.exe)" );
					string data = System.Text.Encoding.ASCII.GetString ( buffer, 0, bytesReceived );
					Trace.WriteLineIf ( traceLevel.TraceInfo, String.Format ( "Received: {0}", data ), "Info (Server.exe)" );
					data = data.ToUpper ( );
					byte [ ] msg = System.Text.Encoding.ASCII.GetBytes ( data );
					Trace.WriteLineIf ( traceLevel.TraceInfo, "Sending response message.", "Info (Server.exe)" );
					stream.Write ( msg, 0, msg.Length );
					Trace.WriteLineIf ( traceLevel.TraceInfo, string.Format( "Sent: {0}", data ), "Info (Server.exe)" );
				}
			}
			catch ( System.Exception ex )
			{
				Trace.WriteLine ( TraceFormatter.FormatException( ex), "Error (Server.exe)" );
				// Notify the server about an unexpected client shutdown.
				this.OnDisconnecting( ex.Message );
			}
			finally
			{
				// Shutdown and end connection
				Trace.WriteLineIf ( traceLevel.TraceInfo, string.Format( "Disconnecting client {0}.", this.id ), "Info (Server.exe)" );
				this.connection.GetStream().Close();
				this.connection.Close ( );
				// Notify the server about an unexpected client shutdown.
				this.OnDisconnected( );
				Trace.WriteLineIf ( traceLevel.TraceInfo, string.Format( "Client {0} disconnected.", this.id  ), "Info (Server.exe)" );
			}
			Trace.WriteLineIf ( traceLevel.TraceInfo, "ClientConnection.Work() exit", "Info (Server.exe)" );
		}
	}
}
